AWS Amplify + Reactで既存のLambda(Python)にファイルを渡して処理する
「AWS Amplify + Reactで既存のLambdaを呼び出す」では、既存のLambdaをAWS Amplify + Reactから呼び出す方法について紹介しました。
今回は、この発展形でLambdaに対してファイルを渡す方法、Reactから見るとファイルアップロードを行う方法について記載します。
解法
フロントエンド(React)
非常にシンプルなReactでの実装例になります。
import React, { useState } from 'react' import { Auth } from 'aws-amplify' import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda' import Box from '@mui/material/Box' import Button from '@mui/material/Button' const initialState = { file: null, someMetadata: '' } const SomeFileUploader = () => { const [formState, setFormState] = useState(initialState) const setInput = (key, value) => { setFormState({ ...formState, [key]: value }) } const convertBase64 = (file) => { return new Promise((resolve, reject) => { const fileReader = new FileReader() fileReader.readAsDataURL(file) fileReader.onload = () => { resolve(fileReader.result) } fileReader.onerror = (error) => { reject(error) } }) } const onFileInputChange = async (event) => { const file = event.target.files[0] const base64 = await convertBase64(file) setInput('file', base64.replace(/^data:\w+\/\w+;base64,/, '')) } const uploadFile = async() => { if (!formState.file) return // メタデータも適当に設定(ファイルアップロードには無関係) formState.someMetadata = 'メタデータです' const credentials = await Auth.currentCredentials() const client = new LambdaClient({ credentials: Auth.essentialCredentials(credentials), region: process.env.REACT_APP_LAMBDA_REGION }) const input = { FunctionName: 'some-existing-lambda-' + process.env.REACT_APP_STAGE, Payload: JSON.stringify(formState) } const command = new InvokeCommand(input) const response = await client.send(command) if (response) { setFormState(initialState) } else { console.error(response.error) } } return ( <Box> <input type="file" onChange={onFileInputChange} /> <Button onClick={uploadFile} text="Upload File" /> </Box> ) } export default SomeFileUploader
Reactからsome-existing-lambda-dev
という名前の既存のLambdaを呼び出す例になっています。
.env
ファイルには下記のような設定をしています。
REACT_APP_STAGE=dev REACT_APP_LAMBDA_REGION=ap-northeast-1
ファイルアップロードに関する箇所を以下にピックアップします。
<input type="file" onChange={onFileInputChange} />
でファイル選択のUIを作成します。このUIでは選択したファイルが異なる度にonChange
に指定したonFileInputChange
がコールされます。
onFileInputChange
は次のようになっています。
const onFileInputChange = async (event) => { const file = event.target.files[0] const base64 = await convertBase64(file) setInput('file', base64.replace(/^data:\w+\/\w+;base64,/, '')) }
まず、event.target.files[0]
で選択したファイルオブジェクトの参照を取得し、convertBase64
関数に渡してファイルコンテンツをBase64エンコードします。Base64エンコード結果は
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA.........
のようにメタ情報が最初についてきます。このメタ情報(〜base64,
まで)はファイルコンテンツのデータとしては不要なので、
base64.replace(/^data:\w+\/\w+;base64,/, '')
で除去し、formState.file
に結果をセットしています。
あとはaws-sdk
のLambda Clientを使ってLambdaを呼び出しています。
See Also
バックエンド(Lambda)
PythonによるLambdaの実装例を示します。 フロントエンドでアップロード指定したファイルを受け取ってS3に保存します。
import os import logging import boto3 import botocore.exceptions import json import base64 logging.basicConfig( format="%(asctime)s %(module)s:%(funcName)s(%(lineno)d) - %(levelname)s - %(message)s" ) logger = logging.getLogger() logger.setLevel(logging.INFO) bucket_name = os.environ['S3_BUCKET'] upload_dir = os.environ['UPLOAD_DIR'] def lambda_handler(event, context): s3 = boto3.resource('s3') bucket = s3.Bucket(bucket_name) key = "%s/%s" % (upload_dir, event['filename']) body = base64.b64decode(event['file']) try: bucket.put_object( Key = key, Body = body, Metadata = { 'some-metadata': event['someMetadata'] } ) logger.info("S3にアップロードしました。 key: %s" % key) except Exception as e: logger.error("S3へのアップロードでエラーが発生しました。処理を中断します。: Exception name is %s. Detail is %s" % (type(e).__name__, e)) raise ret_body = { "input": event } return { "statusCode": 200, "body": json.dumps(ret_body) } # メイン関数 if __name__ == "__main__": lambda_handler({"filename": "", "someMetadata": ""}, {})
event['file']
にBase64エンコードしたファイルコンテンツが入っていますので、
body = base64.b64decode(event['file'])
でデコードしてbucket.put_object()
メソッドにBody
として渡すことでS3に保存できます。
なお、put_objectメソッドではMetadata
にdictを渡すと、dictのkey名のカスタムメタデータをオブジェクトに対して設定できます。ここではsome-metadata
というカスタムメタデータに対して、Reactで入力したevent['someMetadata']
を指定しています。